package com.opennews.openplatform.service;

import com.opennews.openplatform.dto.account.group.AccountGroupDto;
import com.opennews.openplatform.entity.tables.records.AccountGroupRecord;
import com.opennews.openplatform.entity.tables.records.AccountGroupUserRecord;
import com.opennews.openplatform.entity.tables.records.UserRecord;
import com.opennews.openplatform.entity.tables.records.UserRoleRecord;
import com.opennews.openplatform.myspringbootcore.common.constant.RoleType;
import com.opennews.openplatform.myspringbootcore.util.SharedUtil;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.jooq.DSLContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Map;

import static com.opennews.openplatform.entity.Tables.*;

@RequiredArgsConstructor
@Service
public class AccountGroupService {
    private final DSLContext dslContext;
    private final FileService fileService;
    private final IncomeExpenseDetailTypeService incomeExpenseDetailTypeService;
    private final RoleService roleService;
    private final UserService userService;
    private final UserActiveTokenService userActiveTokenService;

    /**
     * Queries user's account group info by user account group id.
     *
     * @param accountGroupId: The string of the user account group id.
     * @return The map instance which contains keys below.
     * id: String of the user account group id.
     * title: String of the title.
     */
    public Map<String, Object> queryAccountGroupInfo(String accountGroupId, String userId) {
        Map<String, Object> owner = dslContext.select(
                        USER.ID.as("id"),
                        USER.USERNAME.as("username"),
                        USER.FULL_NAME.as("fullName")
                )
                .from(ACCOUNT_GROUP_USER)
                .join(USER).on(ACCOUNT_GROUP_USER.USER_ID.eq(USER.ID))
                .where(
                        ACCOUNT_GROUP_USER.ACCOUNT_GROUP_ID.eq(accountGroupId)
                                .and(ACCOUNT_GROUP_USER.IS_ACCOUNT_GROUP_CREATOR.isTrue())
                )
                .fetchOneMap();

        return Map.of(
                "id", accountGroupId,
                "isOwner", owner.get("id").equals(userId),
                "ownerId", owner.get("id"),
                "ownerUsername", owner.get("username"),
                "ownerFullName", owner.get("fullName")
        );
    }

    /**
     * Creates a new AccountGroup and a new User.
     * This typically used by new user register.
     * During register the username and password are only needed.
     * The other details can be added later.
     *
     * @param username: The username used for login.
     * @param password: The password used for login.
     * @param fullName: The full name used for display.
     */
    @Transactional
    public void register(String username, String password, String fullName) {
        // Gets the ACCOUNT_GROUP_ADMIN role id.
        String roleId = roleService.queryId(RoleType.ROLE_ACCOUNT_GROUP_ADMIN);

        // Creates a new AccountGroup which will be used by the new User.
        AccountGroupRecord accountGroup = dslContext.newRecord(ACCOUNT_GROUP);
        accountGroup.setTitle("我的账户");
        accountGroup.insert();

        // Prepares the default income/expense detail type for the created account group.
        incomeExpenseDetailTypeService.init(accountGroup.getId());

        // Creates a new User with the created AccountGroup and username, password.
        UserRecord user = dslContext.newRecord(USER);
        user.setAccountGroupId(accountGroup.getId());
        user.setUsername(username);
        user.setPassword(password);
        user.setFullName(fullName);
        user.insert();

        // Defines relationship between account group and user.
        AccountGroupUserRecord accountGroupUser = dslContext.newRecord(ACCOUNT_GROUP_USER);
        accountGroupUser.setAccountGroupId(accountGroup.getId());
        accountGroupUser.setUserId(user.getId());
        accountGroupUser.setIsAccountGroupCreator(true);
        accountGroupUser.insert();

        // Assigns the default role to the created user.
        UserRoleRecord userRole = dslContext.newRecord(USER_ROLE);
        userRole.setAccountGroupId(accountGroup.getId());
        userRole.setUserId(user.getId());
        userRole.setRoleId(roleId);
        userRole.insert();
    }

    /**
     * IMPORTANT!!!
     * THIS ACTION CANNOT BE UNDONE.
     * Clears own account group, including users and all data and quits from all other account groups.
     *
     * @param userId: String of user id.
     */
    @Transactional
    public void unregister(String userId) {
        // Queries user's avatar filename.
        String avatarFilename = dslContext.selectFrom(USER)
                .where(USER.ID.eq(userId))
                .fetchOne(USER.AVATAR_FILENAME);

        // Queries user's own account group id.
        String accountGroupId = dslContext.selectFrom(ACCOUNT_GROUP_USER)
                .where(
                        ACCOUNT_GROUP_USER.USER_ID.eq(userId)
                                .and(ACCOUNT_GROUP_USER.IS_ACCOUNT_GROUP_CREATOR.isTrue())
                )
                .fetchOne(ACCOUNT_GROUP_USER.ACCOUNT_GROUP_ID);

        if (!SharedUtil.isNullOrEmpty(accountGroupId)) {
            // All users with this account group as their current account group will be restored to their own.
            userService.restoreAccountGroupByAccountGroupId(accountGroupId);

            // Deletes active tokens which currently logged in to this account group to force re-login.
            userActiveTokenService.deleteByAccountGroup(accountGroupId);
        }

        // Removes current user from all other account groups.
        // Removes all users from this account group.
        dslContext.deleteFrom(ACCOUNT_GROUP_USER)
                .where(
                        ACCOUNT_GROUP_USER.ACCOUNT_GROUP_ID.eq(accountGroupId)
                                .or(ACCOUNT_GROUP_USER.USER_ID.eq(userId))
                )
                .execute();

        // Clears all the data of this account group.
        dslContext.deleteFrom(JOURNEY_INCOME_EXPENSE_DETAIL)
                .where(JOURNEY_INCOME_EXPENSE_DETAIL.ACCOUNT_GROUP_ID.eq(accountGroupId))
                .execute();

        dslContext.deleteFrom(JOURNEY)
                .where(JOURNEY.ACCOUNT_GROUP_ID.eq(accountGroupId))
                .execute();

        dslContext.deleteFrom(INCOME_EXPENSE_DETAIL)
                .where(INCOME_EXPENSE_DETAIL.ACCOUNT_GROUP_ID.eq(accountGroupId))
                .execute();

        dslContext.deleteFrom(INCOME_EXPENSE_DETAIL_TYPE)
                .where(INCOME_EXPENSE_DETAIL_TYPE.ACCOUNT_GROUP_ID.eq(accountGroupId))
                .execute();

        dslContext.deleteFrom(BANK_CARD)
                .where(BANK_CARD.ACCOUNT_GROUP_ID.eq(accountGroupId))
                .execute();

        dslContext.deleteFrom(USER_ROLE).where(
                USER_ROLE.ACCOUNT_GROUP_ID.eq(accountGroupId)
                        .or(USER_ROLE.USER_ID.eq(userId))
        ).execute();

        // Deletes all its pending requests.
        dslContext.deleteFrom(JOIN_ACCOUNT_GROUP_REQUEST)
                .where(
                        JOIN_ACCOUNT_GROUP_REQUEST.SENDER_ID.eq(userId)
                                .or(JOIN_ACCOUNT_GROUP_REQUEST.RECEIVER_ID.eq(userId))
                                .or(JOIN_ACCOUNT_GROUP_REQUEST.OWNER_ID.eq(userId))
                )
                .execute();

        // Deletes current user.
        dslContext.deleteFrom(USER)
                .where(USER.ID.eq(userId))
                .execute();

        // Deletes this account group.
        dslContext.deleteFrom(ACCOUNT_GROUP)
                .where(ACCOUNT_GROUP.ID.eq(accountGroupId))
                .execute();

        // Deletes avatar file if exists.
        if (!SharedUtil.isNullOrEmpty(avatarFilename)) {
            fileService.delete(avatarFilename);
        }
    }

    public void update(@Valid AccountGroupDto data) {
        AccountGroupRecord record = dslContext.newRecord(ACCOUNT_GROUP);
        record.from(data);
        record.changed(ACCOUNT_GROUP.ID, false);
        record.update();
    }
}
